﻿using Microsoft.Msagl.Core.Layout;
using Microsoft.Msagl.Core.Routing;
using Microsoft.Msagl.Routing;
using Microsoft.VisualStudio.Diagrams.View;
using Microsoft.VisualStudio.GraphModel;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using MNode = Microsoft.Msagl.Core.Layout.Node;
using MPoint = Microsoft.Msagl.Core.Geometry.Point;
using MRect = Microsoft.Msagl.Core.Geometry.Rectangle;
using MRRect = Microsoft.Msagl.Core.Geometry.Curves.RoundedRect;

namespace Timtopia.DGMLPosterView
{
    public class RenderControl : ContentControl
    {
        Graph graph;
        GeometryGraph geometryGraph;
        private GraphNode node;
        private DiagramControl control;
        public RenderControl(DiagramControl control, GraphNode node)
        {
            this.node = node;
            this.graph = node.Owner;
            this.control = control;
            var panel = new StackPanel();
            this.Content = panel;
            var button1 = new Button() { Content = "Route Power Edges" };
            var button2 = new Button() { Content = "Route Normal Edges" };
            panel.Children.Add(button1);
            panel.Children.Add(button2);
            node.SetZIndex(100000);
            button1.Click += RoutePowerEdges_Click;
            button2.Click += RouteOtherEdges_Click;
        }

        private Cluster GetGroupHierarchy(Dictionary<GraphNode, MNode> nodeLookup)
        {
            var root = new Cluster();
            foreach (var o in graph.OrphanNodes.Where(o => o != this.node))
            {
                GraphGroup group = graph.FindGroup(o);
                if (group != null)
                {
                    root.AddChild(GetGroupHierarchy(group, nodeLookup));
                }
                else
                {
                    root.AddChild(nodeLookup[o]);
                }
            }
            return root;
        }

        private Cluster GetGroupHierarchy(GraphGroup g, Dictionary<GraphNode, MNode> nodeLookup)
        {
            var childClusters = from c in g.ChildGroups
                                select GetGroupHierarchy(c, nodeLookup);
            var childNodes = from node in g.ChildNodes.Where(v => v.Id.ToString() != "RenderControl")
                                select nodeLookup[node];
            Cluster cluster = new Cluster(childNodes, childClusters);
            var gnode = g.AsNode();
            cluster.UserData = gnode;
            nodeLookup[gnode] = cluster;
            cluster.BoundaryCurve = new MRRect(GetNodeRect(g.AsNode()), ModuleGroup.Radius, ModuleGroup.Radius);
            return cluster;
        }

        static MRRect GetNodeShape(GraphNode v)
        {
            Rect bounds = v.GetBounds();
            double r = v.GetNodeRadius();
            return new MRRect(GetNodeRect(v), r, r);
        }

        static MRect GetNodeRect(GraphNode v)
        {
            Rect bounds = v.GetBounds();
            return new MRect(bounds.Left, bounds.Top, new MPoint(bounds.Width, bounds.Height));
        }

        void RoutePowerEdges_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            RouteEdges(l => !l.IsPseudo && l.HasCategory(PowerGraphDecomposition.PowerEdgeCategory));
        }

        void RouteOtherEdges_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            RouteEdges(l =>
            {
                if (!l.HasCategory(PowerGraphDecomposition.PowerEdgeCategory))
                {
                    var sourceParent = l.Source.ParentGroups.FirstOrDefault();
                    var targetParent = l.Target.ParentGroups.FirstOrDefault();
                    if (sourceParent != targetParent)
                    {
                        return false;
                    }
                    return true;
                }
                return false;
            });
        }

        void RouteEdges(Func<GraphLink, bool> edgeSelector) 
        {
            geometryGraph = new GeometryGraph();
            var nodeLookup = new Dictionary<GraphNode, MNode>();
            var edgeLookup = new Dictionary<Edge, GraphLink>();
            foreach (var v in graph.Nodes.Where(node => node != this.node))
            {
                var mNode = new MNode(new MRRect(GetNodeRect(v), 10, 10));
                if (!v.IsGroup)
                {
                    geometryGraph.Nodes.Add(mNode);
                }
                mNode.UserData = v;
                nodeLookup[v] = mNode;
            }
            geometryGraph.RootCluster = GetGroupHierarchy(nodeLookup);
            foreach (var l in graph.Links.Where(l=>!l.HasCategory(GraphCommonSchema.Contains) && edgeSelector(l)))
            {
                var source = nodeLookup[l.Source];
                var target = nodeLookup[l.Target];
                var mEdge = new Edge(source, target);
                object weight = l.GetValue(PowerGraphDecomposition.Weight);
                double? dw = weight as double?;
                double w = dw != null ? (double)weight : 1d;
                mEdge.EdgeGeometry.LineWidth = Math.Log(w) + 1;
                mEdge.EdgeGeometry.TargetArrowhead = new Arrowhead
                {
                    Width = 8, //l.GetLinkTargetDecoratorHeight(),
                    Offset = 1, // l.GetLinkTargetDecoratorTipOffset(),
                    Length = 9 //l.GetLinkTargetDecoratorBaseTipDepth() + l.GetLinkTargetDecoratorTipOffset()
                };
                if (l.Source.IsGroup)
                {
                    mEdge.EdgeGeometry.SourceArrowhead = new Arrowhead
                    {
                        Width = 8, //l.GetLinkTargetDecoratorHeight(),
                        Offset = 1, // l.GetLinkTargetDecoratorTipOffset(),
                        Length = 9 //l.GetLinkTargetDecoratorBaseTipDepth() + l.GetLinkTargetDecoratorTipOffset()
                    };
                }
                geometryGraph.Edges.Add(mEdge);
                edgeLookup[mEdge] = l;
            }

            var settings = new BundlingSettings();
            settings.EdgeSeparation = 0.1;
            try
            {
                var route = new SplineRouter(geometryGraph, 1, 2, Math.PI / 6, settings);
                //var route = new SplineRouter(geometryGraph, 1, 2, Math.PI / 6);
                route.Run();
                this.InvalidateVisual();
            }
            catch (Exception exception)
            {
            }
            //foreach (var edge in geometryGraph.Edges)
            //{
            //    var l = edgeLookup[edge];
            //    l.SetGeometry(MsaglGeometryHelper.ToGeometry(edge));
            //    l.SetLinkTargetDecoratorTip(MsaglGeometryHelper.ToPoint(edge.EdgeGeometry.TargetArrowhead.TipPosition));

            //    var spatialItem = this.control.GetSpatialItem(l);
            //    if (spatialItem == null) continue;
            //    var linkVis = this.control.Canvas.VisualFromItem(spatialItem) as LinkShape;
            //    if (linkVis == null) continue;
            //}
        }

        public static Color ArrowColor;
        public static Brush ArrowBrush;

        static RenderControl()
        {
            ArrowColor = (Color)ColorConverter.ConvertFromString("#d6971f");
            ArrowBrush = new SolidColorBrush(ArrowColor);
        }

        protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            // to ensure that everything on-screen is rendered by this method, uncomment the following line
            // which simply draws a big white rectangle over everything else
            //drawingContext.DrawRectangle(Brushes.White, null, new Rect(-1000, -1000, 2000, 2000));

            Pen normalPen = new Pen(ModuleGroup.ModuleBrush, 2);
            Pen thickPen = new Pen(ModuleGroup.ModuleBrush, 3);
            Pen backgroundPen = new Pen(Brushes.White, 4);
            //backgroundPen.EndLineCap = PenLineCap.Round;
            //backgroundPen.StartLineCap = PenLineCap.Round;
            //backgroundPen.LineJoin = PenLineJoin.Round;
            Pen thinBackgroundPen = new Pen(Brushes.White, 2);
            thinBackgroundPen.EndLineCap = PenLineCap.Round;
            thinBackgroundPen.StartLineCap = PenLineCap.Round;
            thinBackgroundPen.LineJoin = PenLineJoin.Round;
            Pen thickBackgroundPen = new Pen(Brushes.White, 6);
            Rect canvasBounds = this.node.GetBounds();
            Vector renderOffset = -new Vector(canvasBounds.X, canvasBounds.Y);
            drawingContext.PushTransform(new TranslateTransform(renderOffset.X, renderOffset.Y));

            if (geometryGraph != null)
            {
                var groupGeometries = new Dictionary<GraphGroup, List<Geometry>>();
                foreach (var e in geometryGraph.Edges)
                {
                    if (e.EdgeGeometry.SourceArrowhead != null && e.EdgeGeometry.Curve != null)
                    {
                        Point start = ToPoint(e.EdgeGeometry.SourceArrowhead.TipPosition);
                        Point curveStart = ToPoint(e.EdgeGeometry.Curve.Start);
                        var ellipse = new EllipseGeometry(start, 9, 9);
                        //drawingContext.DrawGeometry(Brushes.White, null, ellipse);
                        Rect sourceRect = e.Source.BoundaryCurve.BoundingBox.ToRect();
                        var sourceGeom = new RectangleGeometry(sourceRect, ModuleGroup.Radius, ModuleGroup.Radius);
                        var intersections = GeometryHelper.GetIntersectionPoints(sourceGeom, ellipse);
                        if (intersections.Length == 2)
                        {
                            double trianglePenThickness = 3;
                            double lineWidth = 2 * (e.EdgeGeometry.LineWidth - trianglePenThickness);
                            var pathGeom = lineWidth > 0 ? 
                                GetSourceTriangleGeometry(intersections[0], intersections[1], curveStart, lineWidth)
                                : GetSourceTriangleGeometry(intersections[0], intersections[1], curveStart);
                            //Pen trianglePen = new Pen(ModuleGroup.ModuleBrush, 3);
                            //trianglePen.EndLineCap = PenLineCap.Round;
                            //trianglePen.StartLineCap = PenLineCap.Round;
                            //drawingContext.DrawGeometry(null, trianglePen, pathGeom);
                            var node = e.Source.UserData as GraphNode;
                            if (node != null)
                            {
                                var group = graph.FindGroup(node);
                                if (group != null)
                                {
                                    List<Geometry> geometries;
                                    if (!groupGeometries.TryGetValue(group, out geometries))
                                    {
                                        groupGeometries[group] = geometries = new List<Geometry>();
                                    }
                                    geometries.Add(pathGeom);
                                }
                            }
                        }
                    }
                }
                foreach (var group in graph.AllGroups.Reverse().Where(g => g.IsExpanded))
                {
                    Rect bounds = group.GetBounds();
                    bounds.Inflate(0.1, 0.1);
                    Geometry groupGeom = new RectangleGeometry(bounds, 40, 40);
                    var geometryGroup = new GeometryGroup();
                    geometryGroup.Children.Add(groupGeom);
                    var trianglePen = new Pen(ModuleGroup.ModuleBrush, 2.5);
                    SolidColorBrush fill = null; // new SolidColorBrush(new Color { R = 147, G = 147, B = 255, A = 50 });
                    //drawingContext.DrawRoundedRectangle(null, trianglePen, group.Key.GetBounds(), 40, 40);
                    List<Geometry> geometries;
                    if (groupGeometries.TryGetValue(group, out geometries))
                    {
                        foreach (var geom in geometries)
                        {
                            groupGeom = Geometry.Combine(groupGeom, geom, GeometryCombineMode.Union, null);
                            //trianglePen.EndLineCap = PenLineCap.Round;
                            //trianglePen.StartLineCap = PenLineCap.Round;
                            //drawingContext.DrawGeometry(null, trianglePen, geom);
                        }
                    }
                    //var whitePen = new Pen(Brushes.White, 6);
                    //drawingContext.DrawGeometry(null, whitePen, groupGeom);
                    drawingContext.DrawGeometry(fill, trianglePen, groupGeom);
                }
                foreach (var e in geometryGraph.Edges)
                {
                    Geometry line = e.ToGeometry();
                    Geometry arrow = GetArrowGeometry();
                    TransformGroup tg = new TransformGroup();
                    TranslateTransform tt;
                    RotateTransform rt;
                    Pen pen = e.Source is Cluster || e.Target is Cluster ? thickPen : normalPen;

                    Point basePosition = ToPoint(e.EdgeGeometry.Curve.End);
                    Point tipPosition = ToPoint(e.EdgeGeometry.TargetArrowhead.TipPosition);
                    ComputeTransforms(new Thickness(0), arrow.Bounds.Size, tipPosition, basePosition, out rt, out tt);
                    tg.Children.Add(rt);
                    tg.Children.Add(tt);
                    arrow.Transform = tg;
                    //drawingContext.DrawGeometry(null, pen, line);
                    var gp = new GradientPathLib.GradientPath();
                    gp.StrokeThickness = e.EdgeGeometry.LineWidth;
                    gp.GradientMode = GradientPathLib.GradientMode.Parallel;
                    var stops = new GradientStopCollection();
                    stops.Add(new GradientStop(ModuleGroup.ModuleColor, 0));
                    stops.Add(new GradientStop(ArrowColor, 1));
                    gp.GradientStops = new GradientStopCollection(stops);
                    gp.Data = line;
                    //gp.Tolerance = 0.001;
                    drawingContext.DrawGeometry(Brushes.White, thinBackgroundPen, arrow);
                    drawingContext.DrawGeometry(ArrowBrush, null, arrow);
                    drawingContext.DrawGeometry(null, backgroundPen, line);
                    gp.Render(drawingContext);
                }
                //foreach (var v in geometryGraph.Nodes)
                //{
                //    double r = ((MRRect)v.BoundaryCurve).RadiusX;
                //    Rect rect = v.BoundingBox.ToRect();
                //    Pen blackPen = new Pen(Brushes.Black, normalPen.Thickness);
                //    drawingContext.DrawRoundedRectangle(Brushes.Black, blackPen, rect, 10, 10);
                //    var node = v.UserData as GraphNode;
                //    string label = "";
                //    if (node != null)
                //    {
                //        label = node.Label;
                //    }
                //    var group = v.UserData as GraphGroup;
                //    if (group != null)
                //    {
                //        label = group.Label;
                //    }
                //    FormattedText formattedText = new FormattedText(
                //        label,
                //        System.Globalization.CultureInfo.GetCultureInfo("en-us"),
                //        FlowDirection.LeftToRight,
                //        new Typeface("Times"),
                //        18,
                //        Brushes.White);
                //    //formattedText.SetFontWeight(FontWeights.Bold);
                //    drawingContext.DrawText(formattedText, new Point(rect.Left + (rect.Width - formattedText.Width) / 2.0, rect.Top + (rect.Height - formattedText.Height) / 2.0));

                //}
            }
            drawingContext.Pop();
        }

        private static Geometry GetSourceTriangleGeometry(Point a, Point b, Point c, double lineWidth)
        {
            Vector ab = b - a;
            ab.Normalize();
            ab *= lineWidth / 2.0;
            Point cl = c - ab;
            Point cr = c + ab;
            Point a_ = Mid(b, c);
            Point b_ = Mid(a, c);
            Point c_ = Mid(a, b);
            Point cl_ = c_ - ab;
            Point cr_ = c_ + ab;
            Point xl = Intersect(a, a_, cl, cl_);
            Point xr = Intersect(b, b_, cr, cr_);
            Point A = Mid(a, cl_);
            Point B = Mid(b, cr_);
            Point Cl = Mid(cl, xl);
            Point Cr = Mid(cr, xr);
            PathGeometry pg = new PathGeometry();
            pg.Figures = new PathFigureCollection();
            var segments = new PathSegmentCollection();
            segments.Add(new BezierSegment(A, Cl, cl, isStroked: true));
            segments.Add(new LineSegment(cr, isStroked: true));
            segments.Add(new BezierSegment(Cr, B, b, isStroked: true));
            var pf = new PathFigure(a, segments, closed: true);
            pg.Figures.Add(pf);
            return pg;
        }

        private static Geometry GetSourceTriangleGeometry(Point a, Point b, Point c)
        {
            return GetSourceTriangleGeometry(a, b, c, 0.01);
            //Point a_ = Mid(b, c);
            //Point b_ = Mid(a, c);
            //Point c_ = Mid(a, b);
            //Point x = Intersect(a, a_, b, b_);
            //Point A = Mid(a, c_);
            //Point B = Mid(b, c_);
            //Point C = Mid(c, x);
            //PathGeometry pg = new PathGeometry();
            //pg.Figures = new PathFigureCollection();
            //var segments = new PathSegmentCollection();
            //segments.Add(new BezierSegment(A, C, c, isStroked: true));
            ////PathFigure pf = new PathFigure(a, segments, closed: true);
            ////pg.Figures.Add(pf);
            ////segments = new PathSegmentCollection();
            //segments.Add(new BezierSegment(C, B, b, isStroked: true));
            //// pf = new PathFigure(c, segments, closed: true);
            //PathFigure pf = new PathFigure(a, segments, closed: true);
            //pg.Figures.Add(pf);
            //return pg;
        }

        private static Point Mid(Point a, Point b)
        {
            return new Point((a.X + b.X) / 2d, (a.Y + b.Y) / 2d);
        }

        private static Point Intersect(Point s1V1, Point s1V2, Point s2V1, Point s2V2)
        {
            Point A = s1V1;
            Point B = s1V2;
            Point C = s2V1;
            Point D = s2V2;
            Vector E = B - A;
            Vector F = D - C;
            Vector P = new Vector(-E.Y, E.X);
            double h = ((A - C) * P) / (F * P);
            Debug.Assert(h >= 0);
            Debug.Assert(h <= 1);

            return C + F * h;
        }

        private static Point ToPoint(MPoint p)
        {
            return new Point(p.X, p.Y);
        }

        private static void ComputeTransforms(Thickness margin, Size shapeSize, Point tipPosition, Point basePosition, out RotateTransform rotate, out TranslateTransform translate)
        {
            Vector vector = (Vector)(tipPosition - basePosition);
            double angle = -Vector.AngleBetween(vector, new Vector(1.0, 0.0));
            rotate = new RotateTransform(angle, -margin.Left + shapeSize.Width, -margin.Top + (shapeSize.Height / 2.0));
            translate = new TranslateTransform(tipPosition.X - shapeSize.Width, tipPosition.Y - (shapeSize.Height / 2.0));
        }

        private static Geometry GetArrowGeometry()
        {
            double minHeadWidth = 8;
            double baseCurveDepth = 1;
            double depth = 9;
            PathGeometry geometry2 = new PathGeometry();
            PathFigure figure = new PathFigure
            {
                StartPoint = new Point(depth, minHeadWidth / 2.0)
            };
            LineSegment segment = new LineSegment
            {
                Point = new Point(0.0, minHeadWidth)
            };
            figure.Segments.Add(segment);
            QuadraticBezierSegment segment2 = new QuadraticBezierSegment
            {
                Point1 = new Point(baseCurveDepth, (2.0 * minHeadWidth) / 3.0),
                Point2 = new Point(baseCurveDepth, minHeadWidth / 2.0)
            };
            figure.Segments.Add(segment2);
            QuadraticBezierSegment segment3 = new QuadraticBezierSegment
            {
                Point1 = new Point(baseCurveDepth, minHeadWidth / 3.0),
                Point2 = new Point(0.0, 0.0)
            };
            figure.Segments.Add(segment3);
            figure.IsClosed = true;
            geometry2.Figures.Add(figure);
            return geometry2;
        }
    }
}
